유틸리티 타입 정리

유틸리티 타입에 대해 알아보자

2023-10-22

마크다운 이미지

유틸리티 타입은 정의해 놓은 타입을 변환할 때 사용하기 좋은 TypeScript가 제공하는 도구이다. 유틸리티 타입을 쓰지 않더라도 기본 문법으로 타입을 변환할 수 있지만 유틸리티 타입을 사용하면 좀 더 간결하게 타입을 변환할 수 있다.

Partial

Type집합의 모든 프로퍼티를 선택적으로 타입을 변환하는 유틸리티 타입이다.

interface Book {
  title: string;
  description: string;
  author: string;
  publisher: string;
}

let typescript: Book = {
  title: "알잘딱깔센 TypeScript",
  description: "타입스크립트 입문자가 읽으면 좋은 책",
}; // 불가능

이 때 Partial을 사용하면 Book의 모든 프로퍼티를 선택적으로 바꿔줌으로서 에러를 잡을 수 있다.

interface Book {
  title?: string;
  description?: string;
  author?: string;
  publisher?: string;
}

let typescript: Partial<Book> = {
  title: "알잘딱깔센 TypeScript",
  description: "타입스크립트 입문자가 읽으면 좋은 책",
}; // 가능

14.2 Required

Required은 Partial과 반대의 개념이다. Partial이 Type 집합의 모든 프로퍼티를 선택적으로 타입을 변환하는 유틸리티 타입이라면, Required은 Type 집합의 모든 프로퍼티를 필수로 설정한 타입을 생성하는 유틸리티 타입이다.

interface Book {
  title: string;
  description: string;
  author?: string;
  publisher?: string;
}

let typescript: Book = {
  title: "알잘딱깔센 TypeScript",
  description: "타입스크립트 입문자가 읽으면 좋은 책",
}; // 가능

하지만 Required 유틸리티 타입을 사용한다면 모든 프로퍼티를 필수로 바꾸기 때문에 에러가 뜬다.

interface Book {
  title: string;
  description: string;
  author: string;
  publisher: string;
}

let typescript: Required<Book> = {
  title: "알잘딱깔센 TypeScript",
  description: "타입스크립트 입문자가 읽으면 좋은 책",
}; // 불가능

14.3 Readonly

Type의 모든 프로퍼티를 재할당이 불가능한 읽기 전용(Readonly) 으로 설정한 타입을 생성한다.

읽기 전용(Readonly)가 아니면 재할당이 가능하다.

interface Book {
  title: string;
  description: string;
  author?: string;
  publisher?: string;
}

let typescript: Book = {
  title: "알잘딱깔센 TypeScript",
  description: "타입스크립트 입문자가 읽으면 좋은 책",
};

typescript.title = "TypeScript Basic"; // 재할당 가능

하지만 Readonly을 준다면 모든 프로퍼티가 읽기 전용(Readonly) 이 되면서 재할당이 불가능하게 된다.

interface Book {
  readonly title: string;
  readonly description: string;
  readonly author?: string;
  readonly publisher?: string;
}

let typescript: Readonly<Book> = {
  title: "알잘딱깔센 TypeScript",
  description: "타입스크립트 입문자가 읽으면 좋은 책",
};

typescript.title = "TypeScript Basic"; // 재할당 불가능

14.4 Record<Keys, Type>

프로퍼티 타입을 Key로, value 타입을 Type으로 지정해 생성한다. Record<Keys,Type> 유틸리티 타입은 프로퍼티를 다른 타입으로 매핑하고 싶을 때 사용하면 좋다.

interface Food {
  "1팀": "피자" | "치킨" | "햄버거" | "컵라면";
  "2팀": "피자" | "치킨" | "햄버거" | "컵라면";
  "3팀": "피자" | "치킨" | "햄버거" | "컵라면";
  "4팀": "피자" | "치킨" | "햄버거" | "컵라면";
}

const food: Food = {
  "1팀": "피자",
  "2팀": "치킨",
  "3팀": "햄버거",
  "4팀": "컵라면",
};

value 값의 반복이 보인다. 이를 Record<Keys,Type>을 활용하면 해결할 수 있다.

const food: Record<
  "1팀" | "2팀" | "3팀" | "4팀",
  "피자" | "치킨" | "햄버거" | "컵라면"
> = {
  "1팀": "피자",
  "2팀": "치킨",
  "3팀": "햄버거",
  "4팀": "컵라면",
};

훨씬 간결해진 모습이지만 조금 길어 보인다. 이를 type을 활용해 정리해 줄 수 있다.

type Team = "1팀" | "2팀" | "3팀" | "4팀";
type Food = "피자" | "치킨" | "햄버거" | "컵라면";

const food: Record<Team, Food> = {
  "1팀": "피자",
  "2팀": "치킨",
  "3팀": "햄버거",
  "4팀": "컵라면",
};

type을 활용해 정리해 주면 재사용에 용이하다.

Pick<Type, Keys>

Type에서 프로퍼티 Keys를 Pick(선택)해 타입을 생성한다.

아래 예제는 Person에서 name과 age를 선택해 kim을 생성한다.

interface Person {
  name: string;
  age: number;
  location: string;
  gender: "M" | "W";
}

const kim: Pick<Person, "name" | "age"> = {
  name: "Kim",
  age: 27,
};

만약 name과 age가 아닌 다른 key를 선택하게 되면 에러가 발생한다.

interface Person {
  name: string;
  age: number;
  location: string;
  gender: "M" | "W";
}

const kim: Pick<Person, "name" | "age"> = {
  name: "Kim",
  location: "Jeju", // Error: Type '{ name: string; location: string; }' is not assignable to type 'Pick<Person, "name" | "age">'

Pick<Type,Keys> 유틸리티 역시 type을 사용해 정리할 수 있다.

interface Person {
  name: string;
  age: number;
  location: string;
  gender: "M" | "W";
}

type Kim = Pick<Person, "name" | "age">;

const kim: Kim = {
  name: "Kim",
  age: 27,
};

Omit<Type, Keys>

Pick 유틸리티 타입과는 반대 개념으로 Type에서 프로퍼티 Keys를 Omit(생략)해 타입을 생성한다.

아래 예제는 Person에서 age와 gender를 생략해 kim을 생성한다.

interface Person {
  name: string;
  age: number;
  location: string;
  gender: "M" | "W";
}

const kim: Omit<Person, "age" | "gender"> = {
  name: "Kim",
  location: "Jeju",
};

만약 age나 gender를 포함하게 되면 에러가 발생한다.

interface Person {
  name: string;
  age: number;
  location: string;
  gender: "M" | "W";
}

const kim: Omit<Person, "age" | "gender"> = {
  name: "Kim",
  location: "Jeju",
  gender: "M", // Error: Type '{ name: string; location: string; gender: string; }' is not assignable to type 'Omit<Person, "age" | "gender">'
};

Omit<Type,Keys> 유틸리티 역시 type을 사용해 정리할 수 있다.

interface Person {
  name: string;
  age: number;
  location: string;
  gender: "M" | "W";
}

type Kim = Omit<Person, "age" | "gender">;

const kim: Kim = {
  name: "Kim",
  location: "Jeju",
};

Exclude<Type, ExcludeUnion>

ExcludeUnion에 들어갈 수 있는 모든 유니온을 Type에서 제외한 타입을 생성한다.

type T1 = Exclude<"kim" | "lee" | "park", "park">;
// result : type T1 = "kim" | "lee"

type T2 = Exclude<string | number | boolean, number | boolean>;
// result : type T2 = string

type T1에서 "park" 을 제외한 "kim" | "lee"가 남게 된다. type T2 역시 number와 boolean 을 제외한 string이 남게 된다.

type T1 = Exclude<string | number | boolean, number>;
// result : type T1 = string | boolean

type T2 = Exclude<T1, boolean>;
// result : type T2 = string

14.6 NonNullable

Type에서 null과 undefined를 제외하고 타입을 생성한다.

type T1 = NonNullable<string | number | boolean | null | undefined>;
// result : type T1 = string | number | boolean

T1 에서 null과 undefined을 제외한 string과 number, boolean이 남게 된다.